#include <stdio.h>
#include <vector>
#include "myMatrix.h"


#include <cassert>
#include <cmath>
#include <cstddef>  // size_t
#include <chrono>
//#include <future>
#include <iostream>

#define TEST_CASE_IN() printf("test case[%s] in++ \n", __FUNCTION__)
#define TEST_CASE_OUT() printf("test case[%s] out-- \n", __FUNCTION__)

using namespace std;

void TestConstructors() 
{
    TEST_CASE_IN();
    
    Matrix<int> a(2, 4, 3);
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 4; ++j)
        {
            assert(a(i, j) == 3);
        }
    }
    Matrix<int> b(3, 5);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 5; ++j) 
        {
            assert(b(i, j) == 0);
        }
    }
    
    Matrix<int> c;
    assert(c.row_size() == 0);
    assert(c.column_size() == 0);
    Matrix<int> d(a);
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(d(i, j) == 3);
        }
    }
    
    Matrix<int> e(Matrix<int>(2, 4, 3));
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(e(i, j) == 3);
        }
    }
    
    Matrix<int> f(std::move(e));
    for (int i = 0; i < 2; ++i)
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(f(i, j) == 3);
        }
    }
    
    Matrix<int> g = 
    {
        { 0, 1, 2 },
        { 1, 2, 3 },
    };
    
    assert(g.row_size() == 2);
    assert(g.column_size() == 3);
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 3; ++j) 
        {
            assert(g(i, j) == i + j);
        }
    }
    
    TEST_CASE_OUT();
}

void TestObservers() 
{
    TEST_CASE_IN();
    Matrix<int> a(2, 4, 3), b;
    assert(a.row_size() == 2);
    assert(a.column_size() == 4);
    assert(a.size() == 2 * 4);
    assert(a.shape().first == 2);
    assert(a.shape().second == 4);
    assert(!a.empty());
    assert(b.empty());
    const Matrix<int>& ra(a);
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(a(i, j) == 3);
            assert(ra(i, j) == 3);
            assert(a[i][j] == 3);
            assert(ra[i][j] == 3);
            a(i, j) = 4;
            assert(a(i, j) == 4);
            assert(a[i][j] == 4);
            a[i][j] = 5;
            assert(a(i, j) == 5);
            assert(a[i][j] == 5);
        }
    }
    
    TEST_CASE_OUT();
}

void TestAssignments() 
{
    TEST_CASE_IN();
    Matrix<int> a(2, 4, 3), b, c;
    b = a;
    assert(a.row_size() == b.row_size());
    assert(a.column_size() == b.column_size());
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(a[i][j] == b[i][j]);
        }
    }
    b = b;
    assert(a.row_size() == b.row_size());
    assert(a.column_size() == b.column_size());
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(a[i][j] == b[i][j]);
        }
    }
    c = std::move(b);
    assert(a.row_size() == c.row_size());
    assert(a.column_size() == c.column_size());
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(a[i][j] == c[i][j]);
        }
    }
    c = std::move(c);
    assert(a.row_size() == c.row_size());
    assert(a.column_size() == c.column_size());
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(a[i][j] == c[i][j]);
        }
    }
    
    TEST_CASE_OUT();
}

void TestManipulators() 
{
    TEST_CASE_IN();
    Matrix<int> a(3, 5, 3);
    a.row_resize(4, 30);
    assert(a.row_size() == 4);
    assert(a.column_size() == 5);
    for (int i = 0; i < 4; ++i) 
    {
        for (int j = 0; j < 5; ++j) 
        {
            assert(a[i][j] == (i<3 ? 3 : 30));
        }
    }
    
    a.row_resize(2);
    assert(a.row_size() == 2);
    assert(a.column_size() == 5);
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 5; ++j) 
        {
            assert(a[i][j] == 3);
        }
    }
    
    a.column_resize(7, 70);
    assert(a.row_size() == 2);
    assert(a.column_size() == 7);
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 7; ++j) 
        {
            assert(a[i][j] == (j<5 ? 3 : 70));
        }
    }
    
    a.column_resize(4);
    assert(a.row_size() == 2);
    assert(a.column_size() == 4);
    for (int i = 0; i < 2; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(a[i][j] == 3);
        }
    }
    
    TEST_CASE_OUT();
}

void TestArithmetrics_Plus()
{
    TEST_CASE_IN();
    Matrix<int> a(3, 4, 10), b(3, 4, 20);
    a += b;
    assert(a.row_size() == 3);
    assert(a.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(a[i][j] == 30);
        }
    }
    
    Matrix<int> d = a + b;
    assert(d.row_size() == 3);
    assert(d.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(d[i][j] == 50);
        }
    }
    
    Matrix<int> e = Matrix<int>(3, 4, 40) + b;
    assert(e.row_size() == 3);
    assert(e.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(e[i][j] == 60);
        }
    }
    
    Matrix<int> f = b + Matrix<int>(3, 4, 40);
    assert(f.row_size() == 3);
    assert(f.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(f[i][j] == 60);
        }
    }
    
    TEST_CASE_OUT();
}

void TestArithmetrics_Minus() 
{
    TEST_CASE_IN();
    Matrix<int> a(3, 4, 10), b(3, 4, 20), c(a);
    c -= b;
    assert(c.row_size() == 3);
    assert(c.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(c[i][j] == -10);
        }
    }
    
    Matrix<int> d = a - b;
    assert(d.row_size() == 3);
    assert(d.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(d[i][j] == -10);
        }
    }
    
    Matrix<int> e = Matrix<int>(3, 4, 40) - b;
    assert(e.row_size() == 3);
    assert(e.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(e[i][j] == 20);
        }
    }
    
    Matrix<int> f = b - Matrix<int>(3, 4, 40);
    assert(f.row_size() == 3);
    assert(f.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(f[i][j] == -20);
        }
    }
    
    TEST_CASE_OUT();
}

void TestArithmetrics_MultiplyByValue() 
{
    TEST_CASE_IN();
    Matrix<int> a(3, 4, 10), b(3, 4, 20), c(a);
    c *= 3;
    assert(c.row_size() == 3);
    assert(c.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(c[i][j] == 30);
        }
    }
    
    Matrix<int> d = a * 4;
    assert(d.row_size() == 3);
    assert(d.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(d[i][j] == 40);
        }
    }
    
    Matrix<int> e = 4 * a;
    assert(e.row_size() == 3);
    assert(e.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(e[i][j] == 40);
        }
    }
    
    Matrix<int> f = 4 * Matrix<int>(3, 4, 10);
    assert(f.row_size() == 3);
    assert(f.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(f[i][j] == 40);
        }
    }
    
    Matrix<int> g = Matrix<int>(3, 4, 10) * 4;
    assert(g.row_size() == 3);
    assert(g.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(g[i][j] == 40);
        }
    }
    
    TEST_CASE_OUT();
}

void TestArithmetrics_DivideByValue() 
{
    TEST_CASE_IN();
    Matrix<int> a(3, 4, 10), b(3, 4, 20), c(a);
    c /= 2;
    assert(c.row_size() == 3);
    assert(c.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(c[i][j] == 5);
        }
    }
    
    Matrix<int> d = a / 2;
    assert(d.row_size() == 3);
    assert(d.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(d[i][j] == 5);
        }
    }
    
    Matrix<int> e = Matrix<int>(3, 4, 40) / 4;
    assert(e.row_size() == 3);
    assert(e.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(e[i][j] == 10);
        }
    }
    
    TEST_CASE_OUT();
}

void TestArithmetrics_ModuleByValue() 
{
    TEST_CASE_IN();
    Matrix<int> a(3, 4, 10), b(3, 4, 20), c(a);
    c %= 3;
    assert(c.row_size() == 3);
    assert(c.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(c[i][j] == 1);
        }
    }
    
    Matrix<int> d = a % 4;
    assert(d.row_size() == 3);
    assert(d.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(d[i][j] == 2);
        }
    }
    
    Matrix<int> e = Matrix<int>(3, 4, 10) % 7;
    assert(e.row_size() == 3);
    assert(e.column_size() == 4);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            assert(e[i][j] == 3);
        }
    }
    
    TEST_CASE_OUT();
}

void TestArithmetrics_MultiplyByMatrix() 
{
    TEST_CASE_IN();
    Matrix<int> a(3, 4, 10), b(4, 5, 20), c(a);
    c *= b;
    assert(c.row_size() == 3);
    assert(c.column_size() == 5);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 5; ++j) 
        {
            assert(c[i][j] == 800);
        }
    }
    
    Matrix<int> d = a * b;
    assert(d.row_size() == 3);
    assert(d.column_size() == 5);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 5; ++j) 
        {
            assert(d[i][j] == 800);
        }
    }
    
    Matrix<int> e = a * Matrix<int>(4, 5, 20);
    assert(e.row_size() == 3);
    assert(e.column_size() == 5);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 5; ++j) 
        {
            assert(e[i][j] == 800);
        }
    }
    
    Matrix<int> f = Matrix<int>(3, 4, 10) * b;
    assert(f.row_size() == 3);
    assert(f.column_size() == 5);
    for (int i = 0; i < 3; ++i) 
    {
        for (int j = 0; j < 5; ++j) 
        {
            assert(f[i][j] == 800);
        }
    }
    
    TEST_CASE_OUT();
}

void TestComparasions() 
{
    TEST_CASE_IN();
    Matrix<int> a = 
    {
        { 1, 2 },
        { 3, 4 },
    };
    
    Matrix<int> b = 
    {
        { 1, 2 },
        { 3, 3 },
    };
    
    Matrix<int> c(a);
    
    assert(a != b);
    assert(a == c);
    assert(a.compare(
           b, [](const int& lhs, const int& rhs) 
           {return abs(lhs - rhs) <= 1;})
          );

    TEST_CASE_OUT();
}

void TestArithmetrics_Temporaries() 
{
    TEST_CASE_IN();
    cout << "prepare a, b, c, scaler, expected" << endl;
    Matrix<int> a = 
    {
        { 1, 2 },
        { 3, 4 },
    };
    
    Matrix<int> b = 
    {
        { 2, 3 },
        { 4, 5 },
    };
    
    Matrix<int> c = 
    {
        { 1, 1 },
        { 1, 1 },
    };
    
    Matrix<int> expected = 
    {
        { 2, 2 },
        { 2, 2 },
    };
    
    int scaler = 3;
    cout << "Matrix<int> d = std::move(c) * scaler + a - b;" << endl;
    Matrix<int> d = std::move(c) * scaler + a - b;
    assert(d.compare(expected));
    cout << "done." << endl;
    
    TEST_CASE_OUT();
}

void TestPrint() 
{
    TEST_CASE_IN();
    Matrix<int> a = 
    {
        { 1, 2 },
        { 3, 4 },
        { 5, 6 },
    };
    
    Matrix<int> b = 
    {
        { 7 , 8 , 9  },
        { 10, 11, 12 },
        { 13, 14, 15 },
    };
    matrix::Print(a, &cout);
    matrix::PrintAugmented(a, b, &cout);
    
    TEST_CASE_OUT();
}

uint64_t NowTimeMs()
{
    std::chrono::time_point<std::chrono::system_clock> p2 =
        std::chrono::system_clock::now();
    return std::chrono::duration_cast<std::chrono::milliseconds>
        (p2.time_since_epoch()).count();
}


void MultiplyByMatrix()
{
    TEST_CASE_IN();
    Matrix<float> a(1, 784, 10);
    Matrix<float> b(784, 200, 20);

    cout << "matrix a.shape(" 
         << a.shape().first << ","
         << a.shape().second << ")" << endl;
    
    cout << "matrix b.shape(" 
         << b.shape().first << ","
         << b.shape().second << ")" << endl;
    
    uint64_t st = NowTimeMs();
    Matrix<float> c = a * b;

    st = NowTimeMs() - st;
    cout << "c->row : " << c.shape().first
        << ", c->clomn: " << c.shape().second
        << ", cast time: " << st << "(microseconds)" << endl;


    TEST_CASE_OUT();
}

void matrix_test()
{
    TestConstructors();
    TestObservers();
    TestAssignments();
    TestManipulators();
    TestArithmetrics_Plus();
    TestArithmetrics_Minus();
    TestArithmetrics_MultiplyByValue();
    TestArithmetrics_DivideByValue();
    TestArithmetrics_ModuleByValue();
    TestArithmetrics_MultiplyByMatrix();
    TestComparasions();
    TestArithmetrics_Temporaries();
    TestPrint();
    MultiplyByMatrix();
}

int main(int argc, char* argv[])
{

    printf("====matrix test start====\n");

    matrix_test();

    printf("====matrix test end====\n");

    getchar();
    return 0;
}